/* * Copyright (C) 2011 4th Line GmbH, Switzerland * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.fourthline.lemma.reader.javacode; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.Parameter; import com.sun.javadoc.RootDoc; import com.sun.javadoc.Type; import org.fourthline.lemma.Constants; import org.fourthline.lemma.anchor.CitationAnchor; import org.fourthline.lemma.pipeline.Context; import org.fourthline.lemma.reader.content.LineRange; import org.fourthline.lemma.reader.content.filter.CleanupFilter; import org.fourthline.lemma.reader.content.filter.ContentFilter; import org.fourthline.lemma.reader.content.filter.FragmentFilter; import org.fourthline.lemma.reader.content.handler.ContentFileHandler; import org.fourthline.lemma.reader.content.printer.ContentPrinter; import org.fourthline.lemma.reader.content.printer.JavaContentPrinter; import org.fourthline.lemma.reader.javadoc.AbstractJavadocReader; import org.seamless.xhtml.XHTML; import org.seamless.xhtml.XHTMLElement; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import java.util.regex.Pattern; /** * Reads raw lines of Java code, handles <code>javacode://</code> scheme. * <p> * This class will discover the source of the citation reference by using the * Javadoc index and {@link org.fourthline.lemma.reader.javacode.LineRangeParser}s. * </p> * * @author Christian Bauer */ public class JavacodeReader extends AbstractJavadocReader { final private Logger log = Logger.getLogger(JavacodeRawReader.class.getName()); final public static Pattern PATTERN_FRAGMENT_LABEL = Pattern.compile("(.*)//\\s*" + Constants.PATTERN_FRAGMENT_LABEL + "\\s*$"); final protected ContentFileHandler handler; final protected ContentPrinter printer; final protected ContentFilter[] filters; final private Map<File, LineRangeParser> lineRangeParsers = new HashMap(); public JavacodeReader() { handler = new ContentFileHandler(); printer = new JavaContentPrinter(); filters = new ContentFilter[]{ new FragmentFilter(PATTERN_FRAGMENT_LABEL), new CleanupFilter(PATTERN_FRAGMENT_LABEL) }; } protected XHTML read(CitationAnchor citation, Context context, RootDoc rootDoc) { return read( findTargetDoc(citation, rootDoc), citation, isGenerateId(context) ); } protected XHTML read(Doc doc, CitationAnchor citation, boolean uniqueId) { if (doc == null) return null; log.fine("Reading Javacode: " + doc.position()); XHTML xhtml = getParser().createDocument(); XHTMLElement root = xhtml.createRoot(getXPath(), Constants.WRAPPER_ELEMENT) .setAttribute(XHTML.ATTR.CLASS, citation.getOutputClasses()); if (uniqueId) root.setAttribute(XHTML.ATTR.id, citation.getOutputIdentifier()); appendTitle(root, citation.getTitle()); addFilePath(root, citation, doc.position().file()); appendContent(root, doc, citation); return xhtml; } protected void appendContent(XHTMLElement parent, Doc doc, CitationAnchor citation) { String[] source = readSource(doc); for (ContentFilter filter : filters) { source = filter.filter(source, citation); } printer.print(source, citation, parent, "prettyprint"); } public String[] readSource(Doc doc) { File file = doc.position().file(); // The type of doc decides if the whole file is returned or just a few lines of the file if (doc instanceof ClassDoc) { ClassDoc classDoc = (ClassDoc) doc; // If it's a nested class, read only the lines of that nested class source if (classDoc.containingClass() == null) { log.finest("Doc is referencing a root type declaration: " + doc.name()); return handler.getContent(file, null); } else { String nestedClassName = classDoc.simpleTypeName(); log.finest("Doc is referencing a nested type declaration: " + nestedClassName); return handler.getContent(file, getLineRangeParser(file).getTypesLineRange().get(nestedClassName)); } } else if (doc instanceof PackageDoc) { // For a package we return everything log.finest("Doc is referencing a package: " + doc.name()); return handler.getContent(file, null); } else if (doc instanceof ExecutableMemberDoc) { // For methods we return the lines of the method source (signature matching is complex though) log.finest("Doc is referencing method declaration: " + doc.name()); return handler.getContent(file, getMethodLineRange(file, (MethodDoc) doc)); } else { log.warning("Unknown doc type/reference, not reading any source: " + doc); } return new String[0]; } public LineRange getMethodLineRange(File file, MethodDoc methodDoc) { LineRangeParser parser = getLineRangeParser(file); String signature = getSignature(methodDoc); log.fine("Looking up source line range of method using signature: " + signature); LineRange range = parser.getMethodsLineRange().get(signature); // Out of options if (range == null) { throw new RuntimeException( "Can't find method line range (begin/end) with signature in source file: " + signature ); } log.fine("Method line range is: " + range); return range; } protected LineRangeParser getLineRangeParser(File file) { synchronized (lineRangeParsers) { if (lineRangeParsers.containsKey(file)) return lineRangeParsers.get(file); try { LineRangeParser parser = instantiateLineRangeParser(file); lineRangeParsers.put(file, parser); return parser; } catch (Exception ex) { throw new RuntimeException(ex); } } } protected LineRangeParser instantiateLineRangeParser(File file) throws Exception { return new LineRangeParser(file); } protected String getSignature(MethodDoc methodDoc) { StringBuilder signature = new StringBuilder(); signature.append(methodDoc.name()); signature.append("("); for (Parameter parameter : methodDoc.parameters()) { signature.append(toString(parameter.type(), false)); // TODO: Always use unqualified name?! signature.append(","); } // Cut last comma if (methodDoc.parameters().length > 0) signature.deleteCharAt(signature.length() - 1); signature.append(")"); return signature.toString(); } protected String toString(Type type, boolean qualified) { return (qualified ? type.qualifiedTypeName() : type.simpleTypeName()) + type.dimension(); } }